/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package technotheist.geo.mesh;

import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import gnu.trove.list.array.TShortArrayList;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;

/**
 *
 * @author Mitchel Strachan
 */
public class PatchUtil {

    private PatchUtil(){}
    
    public static final int NW = 0, SW = 1, SE = 2, NE = 3;
    public static final int NW_MASK = 0x8, SW_MASK = 0x4, SE_MASK = 0x2, NE_MASK = 0x1;
    public static final int SIZE = 16;
    static final VertexBuffer[] LODLevels;
    static final VertexBuffer PatchUV;
    public static final byte MIN_DETAIL = 2, MAX_DETAIL = 6;
    public static final int DETAIL = PatchUtil.MAX_DETAIL;
    public static final int SIDE_QUADS = (1 << DETAIL);
    public static final int AREA_QUADS = SIDE_QUADS * SIDE_QUADS;
    public static final int SIDE_VERTS = SIDE_QUADS + 1;
    public static final int AREA_VERTS = SIDE_VERTS * SIDE_VERTS;
    public static final float STEP = 1.0f / SIDE_QUADS;

    static {
        ArrayList<PatchLODLevel> lod = PatchUtil.solveLODLevels(MIN_DETAIL, MAX_DETAIL);
        LODLevels = new VertexBuffer[1 + (lod.size() - 1) * SIZE];

        ShortBuffer b = BufferUtils.createShortBuffer(lod.get(0).getIndices());
        VertexBuffer vb = new VertexBuffer(Type.Index);
        vb.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedShort, b);
        LODLevels[0] = vb;

        for (int i = 0; i < lod.size() - 1; i++) {
            PatchLODLevel level = lod.get(i + 1);
            for (int j = 0; j < SIZE; j++) {
                b = BufferUtils.createShortBuffer(level.indices[j]);
                vb = new VertexBuffer(Type.Index);
                vb.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedShort, b);
                LODLevels[1 + (i * SIZE) + j] = vb;
            }
        }

        FloatBuffer uv = BufferUtils.createFloatBuffer(AREA_VERTS * 2);
        for (int u = 0; u < SIDE_VERTS; u++) {
            for (int v = 0; v < SIDE_VERTS; v++) {
                int i = 2 * (u + v * SIDE_VERTS);
                uv.put(i, u * STEP);
                uv.put(i + 1, v * STEP);
            }
        }
        PatchUV = new VertexBuffer(Type.TexCoord2);
        PatchUV.setupData(VertexBuffer.Usage.Static, 2, VertexBuffer.Format.Float, uv);
    }

    public static int LevelID(boolean nwFull, boolean swFull, boolean seFull, boolean neFull) {
        return (nwFull ? NW_MASK : 0) | (swFull ? SW_MASK : 0) | (seFull ? SE_MASK : 0) | (neFull ? NE_MASK : 0);
    }

    private static class PatchLODLevel {
//    static final int NW = 0, SW = 1, SE = 2, NE = 3;

        final short[][] indices;

        private PatchLODLevel(byte maxDetail, byte detail, TShortArrayList all) {
//        final short[] indices;
            indices = new short[][]{all.toArray()};
        }

        private PatchLODLevel(byte maxDetail, byte detail, TShortArrayList ctr, TShortArrayList[] full, TShortArrayList[] half) {
//        final short[][] indices;
            indices = new short[SIZE][];
            for (int i = 0; i < SIZE; i++) {
                boolean fnw = (i & NW_MASK) == NW_MASK;
                boolean fsw = (i & SW_MASK) == SW_MASK;
                boolean fse = (i & SE_MASK) == SE_MASK;
                boolean fne = (i & NE_MASK) == NE_MASK;
                TShortArrayList idxs = new TShortArrayList();
                idxs.addAll(ctr);
                idxs.addAll(fnw ? full[NW] : half[NW]);
                idxs.addAll(fsw ? full[SW] : half[SW]);
                idxs.addAll(fse ? full[SE] : half[SE]);
                idxs.addAll(fne ? full[NE] : half[NE]);
                indices[i] = idxs.toArray();
            }
        }

        public short[] getIndices() {
            return getIndices(true, true, true, true);
        }

        public short[] getIndices(boolean nwFull, boolean swFull, boolean seFull, boolean neFull) {
            if (indices.length == SIZE) {
                return indices[PatchUtil.LevelID(nwFull, swFull, seFull, neFull)];
            } else {
                return indices[0];
            }
        }
    }

    private static ArrayList<PatchLODLevel> solveLODLevels(byte minDetail, byte maxDetail) {

        ArrayList<PatchLODLevel> lods = new ArrayList<PatchLODLevel>();

        for (byte d = maxDetail; d > minDetail; d--) {
            //
            TShortArrayList ctr = new TShortArrayList();
            TShortArrayList[] half = new TShortArrayList[]{
                new TShortArrayList(),
                new TShortArrayList(),
                new TShortArrayList(),
                new TShortArrayList()
            };
            TShortArrayList[] full = new TShortArrayList[]{
                new TShortArrayList(),
                new TShortArrayList(),
                new TShortArrayList(),
                new TShortArrayList()
            };

            solveCenter(maxDetail, d, ctr);
            solveNE(maxDetail, d, full[NE], half[NE]);
            solveNW(maxDetail, d, full[NW], half[NW]);
            solveSW(maxDetail, d, full[SW], half[SW]);
            solveSE(maxDetail, d, full[SE], half[SE]);

            lods.add(new PatchLODLevel(maxDetail, d, ctr, full, half));
        }

        TShortArrayList ctr = new TShortArrayList();
        TShortArrayList[] half = new TShortArrayList[]{
            new TShortArrayList(),
            new TShortArrayList(),
            new TShortArrayList(),
            new TShortArrayList()
        };
        TShortArrayList[] full = new TShortArrayList[]{
            new TShortArrayList(),
            new TShortArrayList(),
            new TShortArrayList(),
            new TShortArrayList()
        };

        solveCenter(maxDetail, minDetail, ctr);
        solveNE(maxDetail, minDetail, full[NE], half[NE]);
        solveNW(maxDetail, minDetail, full[NW], half[NW]);
        solveSW(maxDetail, minDetail, full[SW], half[SW]);
        solveSE(maxDetail, minDetail, full[SE], half[SE]);

        ctr.addAll(full[NE]);
        ctr.addAll(full[SE]);
        ctr.addAll(full[SW]);
        ctr.addAll(full[NW]);
        lods.add(new PatchLODLevel(maxDetail, minDetail, ctr));

        Collections.reverse(lods);
        return lods;
    }

    private static void solveCenter(byte maxDetail, byte detail, TShortArrayList full) {

        int maxLen = 1 << maxDetail;
        int len = 1 << detail;
        int step = maxLen / len;


        for (int x = 1; x < len - 1; x += 2) {
            for (int y = 1; y < len - 1; y += 2) {
                short sw = idx((x) * step, (y) * step, maxLen);
                short sc = idx((x + 1) * step, (y) * step, maxLen);
                short se = idx((x + 2) * step, (y) * step, maxLen);
                short cw = idx((x) * step, (y + 1) * step, maxLen);
                short cc = idx((x + 1) * step, (y + 1) * step, maxLen);
                short ce = idx((x + 2) * step, (y + 1) * step, maxLen);
                short nw = idx((x) * step, (y + 2) * step, maxLen);
                short nc = idx((x + 1) * step, (y + 2) * step, maxLen);
                short ne = idx((x + 2) * step, (y + 2) * step, maxLen);
                //1
                full.add(cc);
                full.add(nc);
                full.add(nw);
                //2
                full.add(cc);
                full.add(nw);
                full.add(cw);
                //3
                full.add(cc);
                full.add(cw);
                full.add(sw);
                //4
                full.add(cc);
                full.add(sw);
                full.add(sc);
                //5
                full.add(cc);
                full.add(sc);
                full.add(se);
                //6
                full.add(cc);
                full.add(se);
                full.add(ce);
                //7
                full.add(cc);
                full.add(ce);
                full.add(ne);
                //
                full.add(cc);
                full.add(ne);
                full.add(nc);
            }
        }
    }

    private static void solveNE(byte maxDetail, byte detail, TShortArrayList full, TShortArrayList half) {
        int maxLen = 1 << maxDetail;
        int len = 1 << detail;
        int step = maxLen / len;
//        TShortArrayList full = new TShortArrayList();
//        TShortArrayList half = new TShortArrayList();

        for (int y = 0; y < len; y += 2) {
            short sw = idx((len - 1) * step, y * step, maxLen);
            short se = idx(len * step, y * step, maxLen);
            short cw = idx((len - 1) * step, (y + 1) * step, maxLen);
            short ce = idx(len * step, (y + 1) * step, maxLen);
            short nw = idx((len - 1) * step, (y + 2) * step, maxLen);
            short ne = idx(len * step, (y + 2) * step, maxLen);

            if (y != 0) {
                full.add(sw);
                full.add(se);
                full.add(cw);
                //
                half.add(sw);
                half.add(se);
                half.add(cw);
            }

            full.add(sw);
            full.add(cw);
            full.add(ce);
            full.add(ce);
            full.add(cw);
            full.add(nw);
            //
            half.add(se);
            half.add(ne);
            half.add(cw);
            //
            if (y != len - 2) {
                full.add(nw);
                full.add(cw);
                full.add(ne);
                //
                half.add(nw);
                half.add(cw);
                half.add(ne);
            }
        }

    }

    private static void solveSW(byte maxDetail, byte detail, TShortArrayList full, TShortArrayList half) {
        int maxLen = 1 << maxDetail;
        int len = 1 << detail;
        int step = maxLen / len;
//        TShortArrayList full = new TShortArrayList();
//        TShortArrayList half = new TShortArrayList();

        for (int y = 0; y < len; y += 2) {
            short sw = idx(0, y * step, maxLen);
            short se = idx(step, y * step, maxLen);
            short cw = idx(0, (y + 1) * step, maxLen);
            short ce = idx(step, (y + 1) * step, maxLen);
            short nw = idx(0, (y + 2) * step, maxLen);
            short ne = idx(step, (y + 2) * step, maxLen);

            if (y != 0) {
                full.add(sw);
                full.add(se);
                full.add(ce);
                //
                half.add(sw);
                half.add(se);
                half.add(ce);
            }

            full.add(sw);
            full.add(ce);
            full.add(cw);
            full.add(cw);
            full.add(ce);
            full.add(nw);
            //
            half.add(sw);
            half.add(ce);
            half.add(nw);
            //
            if (y != len - 2) {
                full.add(nw);
                full.add(ce);
                full.add(ne);
                //
                half.add(nw);
                half.add(ce);
                half.add(ne);
            }
        }

    }

    private static void solveNW(byte maxDetail, byte detail, TShortArrayList full, TShortArrayList half) {
        int maxLen = 1 << maxDetail;
        int len = 1 << detail;
        int step = maxLen / len;
//        TShortArrayList full = new TShortArrayList();
//        TShortArrayList half = new TShortArrayList();

        for (int x = 0; x < len; x += 2) {
            short sw = idx(x * step, (len - 1) * step, maxLen);
            short nw = idx(x * step, maxLen, maxLen);
            short sc = idx((x + 1) * step, (len - 1) * step, maxLen);
            short nc = idx((x + 1) * step, maxLen, maxLen);
            short se = idx((x + 2) * step, (len - 1) * step, maxLen);
            short ne = idx((x + 2) * step, maxLen, maxLen);

            if (x != 0) {
                full.add(sc);
                full.add(nw);
                full.add(sw);
                //
                half.add(sc);
                half.add(nw);
                half.add(sw);
            }

            full.add(nw);
            full.add(sc);
            full.add(nc);
            full.add(nc);
            full.add(sc);
            full.add(ne);
            //
            half.add(nw);
            half.add(sc);
            half.add(ne);
            //
            if (x != len - 2) {
                full.add(se);
                full.add(ne);
                full.add(sc);
                //
                half.add(se);
                half.add(ne);
                half.add(sc);
            }
        }

    }

    private static void solveSE(byte maxDetail, byte detail, TShortArrayList full, TShortArrayList half) {
        int maxLen = 1 << maxDetail;
        int len = 1 << detail;
        int step = maxLen / len;
//        TShortArrayList full = new TShortArrayList();
//        TShortArrayList half = new TShortArrayList();

        for (int x = 0; x < len; x += 2) {
            short sw = idx(x * step, 0, maxLen);
            short nw = idx(x * step, step, maxLen);
            short sc = idx((x + 1) * step, 0, maxLen);
            short nc = idx((x + 1) * step, step, maxLen);
            short se = idx((x + 2) * step, 0, maxLen);
            short ne = idx((x + 2) * step, step, maxLen);

            if (x != 0) {
                full.add(nc);
                full.add(sw);
                full.add(nw);
                //
                half.add(nc);
                half.add(sw);
                half.add(nw);
            }

            full.add(sw);
            full.add(sc);
            full.add(nc);
            full.add(nc);
            full.add(sc);
            full.add(se);
            //
            half.add(sw);
            half.add(se);
            half.add(nc);
            //
            if (x != len - 2) {
                full.add(se);
                full.add(ne);
                full.add(nc);
                //
                half.add(se);
                half.add(ne);
                half.add(nc);
            }
        }

    }

    private static short idx(int x, int y, int len) {
        return (short) (x + y * (len + 1));
    }
}
